package com.hyc.seckillsystem.service.Impl;

import com.hyc.seckillsystem.dao.SeckillGoodsMapper;
import com.hyc.seckillsystem.pojo.SeckillGoods;
import com.hyc.seckillsystem.service.redisService;
import com.hyc.seckillsystem.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @projectName: seckillsystem
 * @package: com.hyc.seckillsystem.service.Impl
 * @className: redisServiceImpl
 * @author: 冷环渊 doomwatcher
 * @description: TODO
 * @date: 2022/3/7 21:30
 * @version: 1.0
 */
@Service
@Slf4j
public class redisServiceImpl implements redisService {
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    SeckillGoodsMapper seckillGoodsMapper;

    @Override
    public void initdata(String goodId, int stockNum) {
        //    存储数据时候，设计好key的规则
        //    当前需求 根据商品id 找到唯一库存

        //    秒杀活动 明确需求，应该是需要预热的，再秒杀的开始之前，将数据库中的数据预热到redis缓存中
        //    确保 redis 中 存储一份数据 用于秒杀时刻使用
        //    定义 key 规则 【goods_id+count】
        redisUtil.set(goodId + "_count", stockNum);

        //    商品信息 存放到一个 对象中
        //    good_id+"_good",{"name":"","desc":""}
    }

    @Override
    public void initdata() {
        List<SeckillGoods> seckillGoods = seckillGoodsMapper.selectList(null);
        if (seckillGoods.size() > 0 && seckillGoods != null) {
            for (SeckillGoods seckillGood : seckillGoods) {
                String goodsId = seckillGood.getGoods_id();
                log.info("新增秒杀商品id=>" + goodsId);
                redisUtil.set(goodsId + "_count", seckillGood.getStock_num());
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String startTime = format.format(seckillGood.getStart_time());
                log.info("新增秒杀商品日期=>" + startTime);
                redisUtil.set(goodsId + "_startTime", startTime);
            }
        } else {
            return;
        }
    }

    @Override
    public String seckill(String userId, String goodsId) {
        //handle(userId, goodsId);
        //handleByTrans((userId, goodsId))
        return handleByLua(userId, goodsId);
    }

    @Override
    public String handle(String userId, String goodsId) {
        /* 秒杀时刻逻辑
         * 判断用户id 的有效性 我们没有用户
         * 判断goodsid的有效性
         *      判断当前是否处于可以秒杀的状态
         *      判断是否有剩余库存
         *      判断用户的秒杀权限（是否秒杀过）
         * 减少库存
         * 生成新的订单
         * */
        String stockkey = goodsId + "_count";
        String startTimekey = goodsId + "_startTime";
        String userkey = goodsId + "_" + userId;
        String dateStr = (String) redisUtil.get(startTimekey);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date startTime = null;
        try {
            startTime = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        if (startTime == null || new Date().before(startTime)) {
            return "秒杀还没开始";
        }
        int stockNum = (int) redisUtil.get(stockkey);
        if (stockNum <= 0) {
            return "秒杀一空";
        }
        if (redisUtil.get(userkey) != null) {
            return "用户秒杀成功过了";
        }
        //减少库存
        redisUtil.decr(stockkey);
        //限额 1 可以这么处理
        //如果限额 多个 可以获取value 自增
        redisUtil.set(goodsId + "_" + userId, 1);
        return userId + "用户秒杀成功";
    }

    //使用lua 脚本 保证原子性 （分布式锁）
    @Override
    public String handleByLua(String userId, String goodsId) {
        String startTimekey = goodsId + "_startTime";
        String dateStr = (String) redisUtil.get(startTimekey);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date startTime = null;
        try {
            startTime = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        if (startTime == null || new Date().before(startTime)) {
            return "秒杀还没开始";
        }
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        //设置脚本类型
        script.setResultType(Long.class);
        script.setScriptSource(new ResourceScriptSource(
                new ClassPathResource("lua/sekill.lua")));
        List<String> keylist = new ArrayList<>();
        keylist.add(userId);
        keylist.add(goodsId);
        Object execute = redisUtil.execute(script, keylist);
        String resultStr = String.valueOf(execute);
        if ("0".equals(resultStr)) {
            return "秒杀库存已经空了";
        } else if ("1".equals(resultStr)) {
            return userId;
        } else if ("2".equals(resultStr)) {
            return "该用户秒杀过了 =>" + userId;
        } else {
            return "秒杀和抢购可能出了点问题，正在紧急维护";
        }
    }


    @Override
    public String handleByTrans(String userId, String goodsId) {
        String stockkey = goodsId + "_count";
        String startTimekey = goodsId + "_startTime";
        String userkey = goodsId + "_" + userId;
        String dateStr = (String) redisUtil.get(startTimekey);
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date startTime = null;
        try {
            startTime = format.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        if (startTime == null || new Date().before(startTime)) {
            return "秒杀还没开始";
        }
        if (redisUtil.get(userkey) != null) {
            return "用户秒杀成功过了";
        }
        SessionCallback sessionCallback = new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                // 编写事务逻辑
                // 调用 watch 开始事务， mutil 开始将之后的命令加入队列 exec 执行队列里的命令
                redisOperations.watch(stockkey);
                int stockNum = (int) redisUtil.get(stockkey);
                if (stockNum <= 0) {
                    return "秒杀一空";
                }
                redisOperations.multi();
                redisUtil.decr(stockkey);
                redisUtil.set(userkey, 1);
                return redisOperations.exec();
            }
        };
        redisUtil.execute(sessionCallback);
        if (redisUtil.hasKey(userkey)) {
            return userId;
        }
        return userId + "秒杀失败";
    }

}
